package org.nutz.aop;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;

import org.nutz.lang.Lang;
import org.nutz.lang.Mirror;

/**
 * 提供ClassAgent的基础实现,拦截不可能插入Aop代码的Class
 * <p/>
 * 传入的Class对象需要满足的条件
 * <li>不能是final或者abstract的
 * <li>必须有非private的构造函数
 * </p>
 * 被拦截的方法需要满足的条件 <li>不能是final或者abstract的 <li>不是private的
 * 
 * @author wendal(wendal1985@gmail.com)
 * 
 */
public abstract class AbstractClassAgent implements ClassAgent {

	private ArrayList<Pair> pairs = new ArrayList<Pair>();

	public ClassAgent addInterceptor(MethodMatcher matcher, MethodInterceptor listener) {
		if (null != listener)
			pairs.add(new Pair(matcher, listener));
		return this;
	}

	public <T> Class<T> define(ClassDefiner cd, Class<T> klass) {
		if (klass.getName().endsWith(CLASSNAME_SUFFIX))
			return klass;
		String newName = klass.getName() + CLASSNAME_SUFFIX;
		Class<T> newClass = try2Load(newName, cd);
		if (newClass != null)
			return newClass;
		if (checkClass(klass) == false)
			return klass;
		Pair2[] pair2s = findMatchedMethod(klass);
		if (pair2s.length == 0)
			return klass;
		Constructor<T>[] constructors = getEffectiveConstructors(klass);
		newClass = generate(cd, pair2s, newName, klass, constructors);
		return newClass;
	}

	protected abstract <T> Class<T> generate(	ClassDefiner cd,
												Pair2[] pair2s,
												String newName,
												Class<T> klass,
												Constructor<T>[] constructors);

	@SuppressWarnings("unchecked")
	protected <T> Constructor<T>[] getEffectiveConstructors(Class<T> klass) {
		Constructor<T>[] constructors = (Constructor<T>[]) klass.getDeclaredConstructors();
		List<Constructor<T>> cList = new ArrayList<Constructor<T>>();
		for (int i = 0; i < constructors.length; i++) {
			Constructor<T> constructor = constructors[i];
			if (Modifier.isPrivate(constructor.getModifiers()))
				continue;
			cList.add(constructor);
		}
		if (cList.size() == 0)
			throw Lang.makeThrow("No non-private constructor founded,unable to create sub-class!");
		return cList.toArray(new Constructor[cList.size()]);
	}

	protected <T> boolean checkClass(Class<T> klass) {
		if (klass == null)
			return false;
		String klass_name = klass.getName();
		if (klass_name.endsWith(CLASSNAME_SUFFIX))
			return false;
		if (klass.isInterface()
			|| klass.isArray()
			|| klass.isEnum()
			|| klass.isPrimitive()
			|| klass.isMemberClass()
			|| klass.isAnnotation()
			|| klass.isAnonymousClass())
			throw Lang.makeThrow("%s is NOT a Top-Class!Creation FAIL!", klass_name);
		if (Modifier.isFinal(klass.getModifiers()) || Modifier.isAbstract(klass.getModifiers()))
			throw Lang.makeThrow("%s is final or abstract!Creation FAIL!", klass_name);
		return true;
	}

	@SuppressWarnings("unchecked")
	protected <T> Class<T> try2Load(String newName, ClassDefiner cd) {
		try {
			return (Class<T>) cd.load(newName);
		}
		catch (ClassNotFoundException e) {
			ClassLoader classLoader = getClass().getClassLoader();
			try {
				return (Class<T>) Class.forName(newName, false, classLoader);
			}
			catch (ClassNotFoundException e2) {
				try {
					return (Class<T>) Lang.loadClass(newName);
				}
				catch (ClassNotFoundException e1) {
					try {
						return (Class<T>) classLoader.loadClass(newName);
					}
					catch (ClassNotFoundException e3) {}
				}
			}
		}
		return null;
	}

	private <T> Pair2[] findMatchedMethod(Class<T> klass) {
		Method[] all = Mirror.me(klass).getAllDeclaredMethodsWithoutTop();
		List<Pair2> p2 = new ArrayList<Pair2>();
		for (Method m : all) {
			int mod = m.getModifiers();
			if (mod == 0 || Modifier.isStatic(mod) || Modifier.isPrivate(mod))
				continue;
			ArrayList<MethodInterceptor> mls = new ArrayList<MethodInterceptor>();
			for (Pair p : pairs)
				if (p.matcher.match(m))
					mls.add(p.listener);
			if (mls.size() > 0)
				p2.add(new Pair2(m, mls));
		}
		return p2.toArray(new Pair2[p2.size()]);
	}

	protected static class Pair {
		Pair(MethodMatcher matcher, MethodInterceptor listener) {
			this.matcher = matcher;
			this.listener = listener;
		}

		MethodMatcher matcher;
		MethodInterceptor listener;
	}

	protected static class Pair2 {
		Pair2(Method method, ArrayList<MethodInterceptor> listeners) {
			this.method = method;
			this.listeners = listeners;
		}

		public Method method;
		public ArrayList<MethodInterceptor> listeners;
	}
}
